/*
 * // Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
 * // ---------------------------------------------------------------------------
 * // This program is free software; you can redistribute it and/or modify it
 * // under the terms of the GNU Lesser General Public License as published by the
 * // Free Software Foundation; either version 3 of the License, or (at your
 * // option) any later version.
 * // This library is distributed in the hope that it will be useful,
 * // but without any warranty; without even the implied warranty of merchantability
 * // or fitness for a particular purpose.
 * // You should have received a copy of the GNU Lesser General Public License along
 * // with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
 * // ---------------------------------------------------------------------------
 */

package pl.bristleback.server.bristle.actions;

import org.apache.log4j.Logger;
import pl.bristleback.server.bristle.exceptions.BristleRuntimeException;
import pl.bristleback.server.bristle.integration.spring.BristleSpringIntegration;
import pl.bristleback.server.bristle.integration.spring.SpringIntegrationUtil;
import pl.bristleback.server.bristle.utils.ActionClassElement;

import java.util.Map;

/**
 * Utility class for creating instances of remote actions.
 * If Spring framework integration is enabled, action class instances may be taken from application context (among with their dependencies).
 * Also injects tokenizer and binder fields.
 * <p/>
 * Created on: 2011-01-03 12:40:34 <br/>
 *
 * @author Wojciech Niemiec
 */
public class ActionInstanceCreator {
  private static Logger log = Logger.getLogger(ActionInstanceCreator.class.getName());

  /**
   * Creates remote action instance using given {@link pl.bristleback.server.bristle.actions.RemoteActionInformation} object.
   * Then, Spring dependencies and tokenizer fields are injected.
   *
   * @param actionInformation information about this remote action class.
   * @return remote action instance.
   */
  public RemoteAction createInstance(RemoteActionInformation actionInformation) {
    RemoteAction instance = initAction(actionInformation.getActionClass());
    injectTokenizerFields(instance, actionInformation);
    injectBinders(instance, actionInformation);

    return instance;
  }

  /**
   * Injects previously created tokenizer fields. Every tokenizer field is created only once per class.
   * It means that every instance of action classes share the same tokenizer objects.
   * Tokenizer instances are created in the same time as action information object.
   * This method performs setting of these instances into newly created remote action object.
   * If tokenizer field cannot be set directly because of security reasons,
   * setter method is searched.
   *
   * @param remoteAction      newly created instance of remote action class.
   * @param actionInformation remote action information.
   */
  private void injectTokenizerFields(RemoteAction remoteAction, RemoteActionInformation actionInformation) {
    for (ActionClassElement tokenizerWrapper : actionInformation.getTokenizerWrappers()) {
      injectField(remoteAction, tokenizerWrapper);
    }
  }

  /**
   * Injects object binders to action class. Binders are created once at plugin start for each action class.
   * This method performs setting of these binders into newly created remote action object.
   * If any field cannot be set directly because of security reasons,
   * setter method is searched.
   *
   * @param remoteAction      newly created instance of remote action class.
   * @param actionInformation remote action information.
   */
  private void injectBinders(RemoteAction remoteAction, RemoteActionInformation actionInformation) {
    for (ActionClassElement objectBinderWrapper : actionInformation.getBinders()) {
      injectField(remoteAction, objectBinderWrapper);
    }
  }

  private void injectField(RemoteAction remoteAction, ActionClassElement actionClassElement) {
    try {
      actionClassElement.getElementField().setAccessible(true);
      actionClassElement.getElementField().set(remoteAction, actionClassElement.getElementValue());
    } catch (IllegalAccessException e) {
      injectFieldUsingSetter(actionClassElement);
    }
  }

  private void injectFieldUsingSetter(ActionClassElement actionClassElement) {
    throw new BristleRuntimeException("Injecting field " + actionClassElement.getElementField()
      + " failed. Setting fields using setters is not implemented yet");   //todo-wojtek implement this
  }

  private RemoteAction initAction(Class<? extends RemoteAction> actionClass) {
    try {
      if (!BristleSpringIntegration.isIntegrationEnabled()) {
        return actionClass.newInstance();
      }
      return initActionWithSpringIntegration(actionClass);
    } catch (Exception e) {
      throw new BristleRuntimeException("Cannot init action class instance.", e);
    }
  }

  private RemoteAction initActionWithSpringIntegration(Class<? extends RemoteAction> actionClass) throws InstantiationException, IllegalAccessException {
    Map beans = SpringIntegrationUtil.getApplicationContext().getBeansOfType(actionClass);
    if (beans.isEmpty()) {
      return actionClass.newInstance();
    }
    int beansAmount = 0;
    Object actionBean = null;
    for (Object bean : beans.values()) {
      if (bean.getClass().equals(actionClass)) {
        beansAmount++;
        actionBean = bean;
      }
    }
    if (beansAmount > 1) {
      if (log.isDebugEnabled()) {
        log.warn("There is more than one Spring bean of type: " + actionClass.getSimpleName() + ".\n"
          + "Action class instance is created using reflection.");
      }
      return actionClass.newInstance();
    } else {
      return (RemoteAction) actionBean;
    }
  }
}
